package com.particles.particles;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.opengl.GLSurfaceView;

import com.particles.particles.objects.Heightmap;
import com.particles.particles.objects.ParticleShooter;
import com.particles.particles.objects.ParticleSystem;
import com.particles.particles.objects.Skybox;
import com.particles.particles.programs.HeightmapShaderProgram;
import com.particles.particles.programs.ParticleShaderProgram;
import com.particles.particles.programs.SkyboxShaderProgram;
import com.particles.particles.util.Geometry;
import com.particles.particles.util.MatrixHelper;
import com.particles.particles.util.TextureHelper;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import static android.opengl.GLES20.GL_BLEND;
import static android.opengl.GLES20.GL_CCW;
import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
import static android.opengl.GLES20.GL_CULL_FACE;
import static android.opengl.GLES20.GL_CW;
import static android.opengl.GLES20.GL_DEPTH_BUFFER_BIT;
import static android.opengl.GLES20.GL_DEPTH_TEST;
import static android.opengl.GLES20.GL_LEQUAL;
import static android.opengl.GLES20.GL_LESS;
import static android.opengl.GLES20.GL_ONE;
import static android.opengl.GLES20.glBlendFunc;
import static android.opengl.GLES20.glClear;
import static android.opengl.GLES20.glClearColor;
import static android.opengl.GLES20.glDepthFunc;
import static android.opengl.GLES20.glDepthMask;
import static android.opengl.GLES20.glDisable;
import static android.opengl.GLES20.glEnable;
import static android.opengl.GLES20.glFrontFace;
import static android.opengl.GLES20.glViewport;
import static android.opengl.Matrix.multiplyMM;
import static android.opengl.Matrix.rotateM;
import static android.opengl.Matrix.scaleM;
import static android.opengl.Matrix.setIdentityM;
import static android.opengl.Matrix.setLookAtM;
import static android.opengl.Matrix.translateM;

/**
 * Created by Cuckoo322 on 4/14/2017.
 */

public class ParticlesRenderer implements GLSurfaceView.Renderer {

    private final Context context;

    private final float[] projectionMatrix = new float[16];
    private final float[] viewMatrix = new float[16];
    private final float[] viewProjectionMatrix = new float[16];

    private final float[] modelMatrix = new float[16];
    private final float[] viewMatrixForSkybox = new float[16];
    private final float[] tempMatrix = new float[16];
    private final float[] modelViewProjectionMatrix = new float[16];

    private ParticleShaderProgram particleShaderProgram;
    private ParticleSystem particleSystem;
    private ParticleShooter redParticleShooter;
    private ParticleShooter greenParticleShooter;
    private ParticleShooter blueParticleShooter;
    private ParticleShooter redParticleShooter_;

    private HeightmapShaderProgram heightmapShaderProgram;
    private Heightmap heightmap;

    private long globalStartTime;

    //设定粒子发射偏转角度（正负都有这么大）
    final float angleVarianceInDegrees = 5f;
    //设定粒子发射速度差异
    final float speedVariance = 1f;

    //纹理
    private int particleTexture;

    //天空盒
    private SkyboxShaderProgram skyboxShaderProgram;
    private Skybox skybox;
    private int skyboxTexture;

    //触控移动导致所需的旋转
    private float xRotation, yRotation;

    public ParticlesRenderer(Context context) {
        this.context = context;
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {

        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

        //打开深度缓冲区
        glEnable(GL_DEPTH_TEST);

        skyboxShaderProgram = new SkyboxShaderProgram(context);
        skybox = new Skybox();
        skyboxTexture = TextureHelper.loadCubeMap(context, new int[]{
                R.drawable.left, R.drawable.right,
                R.drawable.bottom, R.drawable.top,
                R.drawable.front, R.drawable.back
        });

        //初始化地形图及其着色器
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inScaled = false;
        final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.heightmap, options);
        heightmapShaderProgram = new HeightmapShaderProgram(context);
        heightmap = new Heightmap(bitmap);

        //获得纹理
        particleTexture = TextureHelper.loadTexture(context, R.drawable.particle_texture);

        particleShaderProgram = new ParticleShaderProgram(context);
        particleSystem = new ParticleSystem(10000);
        globalStartTime = System.nanoTime();

        final Geometry.Vector particleDirection = new Geometry.Vector(0f, 0.5f, 0f);

        redParticleShooter = new ParticleShooter(
                new Geometry.Point(-1f, 0f, 0f),
                particleDirection,
                Color.rgb(255, 50, 5),
                angleVarianceInDegrees,
                speedVariance
        );
        greenParticleShooter = new ParticleShooter(
                new Geometry.Point(0f, 0f, 0f),
                particleDirection,
                Color.rgb(25, 255, 25),
                angleVarianceInDegrees,
                speedVariance);
        blueParticleShooter = new ParticleShooter(
                new Geometry.Point(1f, 0f, 0f),
                particleDirection,
                Color.rgb(5, 50, 255),
                angleVarianceInDegrees,
                speedVariance);

        redParticleShooter_ = new ParticleShooter(
                new Geometry.Point(-0.5f, 2.5f, 0f),
                particleDirection,
                Color.rgb(255, 50, 5),
                179,
                speedVariance
        );
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        glViewport(0, 0, width, height);

        MatrixHelper.perspectiveM(projectionMatrix, 45, (float) width / (float) height, 1f, 100f);

        updateViewMatrices();

        //通过制定绘制卷曲顺序是逆时针的才绘制，可以对地形另一面的图形进行剔除，节省资源
        glEnable(GL_CULL_FACE);

        //此处设置OpenGL绘制三角形选用的卷曲顺序，CCW是默认的逆时针顺序，则只选逆时针卷曲的三角形绘制，
        // CW则只选顺时针卷曲的三角形绘制
//        glFrontFace(GL_CCW);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        //在每一帧上清空深度缓冲区
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        drawHeightmap();
        drawSkybox();
        drawParticles();
    }

    private void drawSkybox() {
        //改变深度测试算法
        glDepthFunc(GL_LEQUAL);

        setIdentityM(modelMatrix, 0);
        updateMvpMatrixForSkybox();

        skyboxShaderProgram.useProgram();
        skyboxShaderProgram.setUniforms(modelViewProjectionMatrix, skyboxTexture);
        skybox.bindData(skyboxShaderProgram);
        skybox.draw();

        //使深度测试算法回到默认算法（绘制近的不绘制远的）
        glDepthFunc(GL_LESS);
    }

    private void drawParticles() {
        //关闭深度缓冲测试
        glDepthMask(false);

        float currentTime = (System.nanoTime() - globalStartTime) / 1000000000f;

        redParticleShooter.addParticles(particleSystem, currentTime, 5);
        greenParticleShooter.addParticles(particleSystem, currentTime, 5);
        blueParticleShooter.addParticles(particleSystem, currentTime, 5);

        redParticleShooter_.addTrack(particleSystem, currentTime, 5);

        setIdentityM(modelMatrix, 0);
        updateMvpMatrix();

        //使用混合技术
        glEnable(GL_BLEND);
        //设置混合模式为累加模式
        glBlendFunc(GL_ONE, GL_ONE);

        particleShaderProgram.useProgram();
        particleShaderProgram.setUniforms(modelViewProjectionMatrix, currentTime, particleTexture);
        particleSystem.bindData(particleShaderProgram);
        particleSystem.draw();

        //取消使用混合技术
        glDisable(GL_BLEND);

        //粒子绘制完后重新打开深度缓冲测试
        glDepthMask(true);
    }

    public void handleTouchDrag(float deltaX, float deltaY) {
        xRotation += deltaX / 16f;
        yRotation += deltaY / 16f;

        //FPS视角
        if (yRotation < -90) {
            yRotation = -90;
        } else if (yRotation > 90) {
            yRotation = 90;
        }

        updateViewMatrices();
    }

    private void updateViewMatrices() {
        setIdentityM(viewMatrix, 0);
        rotateM(viewMatrix, 0, -yRotation, 1f, 0f, 0f);
        rotateM(viewMatrix, 0, -xRotation, 0f, 1f, 0f);
        System.arraycopy(viewMatrix, 0, viewMatrixForSkybox, 0, viewMatrix.length);

        //矩阵的移动只应用于物体，不应用于天空盒
        translateM(viewMatrix, 0, 0f, -1.5f, -5f);
    }

    private void updateMvpMatrix() {
        multiplyMM(tempMatrix, 0, viewMatrix, 0, modelMatrix, 0);
        multiplyMM(modelViewProjectionMatrix, 0, projectionMatrix, 0, tempMatrix, 0);
    }

    private void updateMvpMatrixForSkybox() {
        multiplyMM(tempMatrix, 0, viewMatrixForSkybox, 0, modelMatrix, 0);
        multiplyMM(modelViewProjectionMatrix, 0, projectionMatrix, 0, tempMatrix, 0);
    }

    private void drawHeightmap() {
        setIdentityM(modelMatrix, 0);
        scaleM(modelMatrix, 0, 100f, 10f, 100f);
        updateMvpMatrix();
        heightmapShaderProgram.useProgram();
        heightmapShaderProgram.setUniforms(modelViewProjectionMatrix);
        heightmap.bindData(heightmapShaderProgram);
        heightmap.draw();
    }
}
